package com.swifty.toptoastbar;

import com.swifty.toptoastbar.curve.AccelerateInterpolator;
import com.swifty.toptoastbar.curve.BounceInterpolator;
import com.swifty.toptoastbar.curve.LinearInterpolator;
import ohos.aafwk.content.Intent;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.app.Context;
import ohos.sysappcomponents.settings.AppSettings;
import ohos.utils.net.Uri;

import java.util.HashSet;
import java.util.Set;

import static com.swifty.toptoastbar.FloatWindowService.SERVICE_WINDOWS_PARENT;

/**
 * Created by swifty on 22/11/2016.
 */
public class Toast extends StackLayout {

    //Add a delay to solve the problem of not displaying animation when displaying
    private static final long BENCHMARK = 32;

    //dropdown duration
    public static final long DEFAULT_DURATION = 800;
    //show time
    public static final long DEFAULT_TIME = 300;
    public static final int LENGTH_LONG = 5000;
    public static final int LENGTH_SHORT = 3000;
    private Intent intent;
    Text messageTextView;
    //-1 means dont dismiss, only worked in the viewgroup
    long time;
    final Set<ToastListener> mToastListeners = new HashSet<>();
    private Animator.TimelineCurve enterInterpolator;
    private Animator.TimelineCurve exitInterpolator;
    private ComponentContainer mViewGroup;
    private Position position;

    public Toast(Context context) {
        super(context);
    }

    public Toast(Context context, AttrSet attrSet) {
        super(context, attrSet);
    }

    public Toast(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
    }


    /**
     * show the toast in the app views.
     *
     * @param viewGroup
     * @param message
     * @param time
     * @return
     */
    protected static Toast make(Position position, ComponentContainer viewGroup, String message, long time) {
        Toast toast = (Toast) LayoutScatter.getInstance(viewGroup.getContext()).parse(ResourceTable.Layout_view_message, viewGroup, false);
        toast.onFinishInflate();
        toast.setText(message)
                .setPosition(position)
                .setTime(time)
                .setViewGroup(viewGroup)
                .setVisibility(INVISIBLE);
        return toast;
    }

    private void setIntent(Intent intent) {
        this.intent = intent;
    }

    private Toast setViewGroup(ComponentContainer viewGroup) {
        this.mViewGroup = viewGroup;
        return this;
    }

    /**
     * show the toast in the android window
     *
     * @param context
     * @param message
     * @param time
     * @return
     */
    protected static Toast make(Position position, Context context, String message, long time) {
        Toast toast = new Toast(context);
        Intent intent = new Intent();
        intent.setParam("message", message);
        intent.setParam("time", time > 0 ? time : DEFAULT_TIME);
        intent.setParam("position", position);
        toast.setIntent(intent);
        return toast;
    }

    private static boolean checkWindowPermission(Context context) {
        if (!AppSettings.canShowFloating(context)) {
            Intent intent = new Intent();
            intent.setOperation(new Intent.OperationBuilder()
                    .withAction("android.settings.action.MANAGE_OVERLAY_PERMISSION")
                    .withUri(Uri.parse("package:" + context.getBundleName()))
                    .build());
            context.startAbility(intent, 0);
            return false;
        }
        return true;
    }

    protected void onFinishInflate() {
        messageTextView = (Text) findComponentById(ResourceTable.Id_message_textview);
    }

    public Toast setAnimationInterpolator(Animator.TimelineCurve enterInterpolator, Animator.TimelineCurve exitInterpolator) {
        this.enterInterpolator = enterInterpolator;
        this.exitInterpolator = exitInterpolator;
        return this;
    }

    /**
     * @param enterInterpolator
     * @return
     * @deprecated use {@link Toast#setAnimationInterpolator(Animator.TimelineCurve, Animator.TimelineCurve)} instead of it.
     */
    public Toast setAnimationInterpolator(Animator.TimelineCurve enterInterpolator) {
        this.enterInterpolator = enterInterpolator;
        this.exitInterpolator = null;
        return this;
    }

    public Toast setBackground(int color) {
        if (color == 0) return this;
        if (intent != null) {
            intent.setParam("backgroundColor", color);
        } else {
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(RgbColor.fromArgbInt(color));
            setBackground(shapeElement);
        }
        return this;
    }

    /**
     * current only for internal use
     *
     * @param toastListener
     * @return this
     */
    Toast addToastListener(ToastListener toastListener) {
        if (toastListener != null) {
            mToastListeners.add(toastListener);
        }
        return this;
    }

    /**
     * current only for internal use
     *
     * @param toastListener
     * @return this
     */
    Toast removeToastListener(ToastListener toastListener) {
        if (toastListener != null) {
            mToastListeners.remove(toastListener);
        }
        return this;
    }

    public Toast setPosition(Position position) {
        this.position = position;

        if (position == Position.BOTTOM) {
            if (getLayoutConfig() instanceof StackLayout.LayoutConfig) {
                ((LayoutConfig) getLayoutConfig()).alignment = LayoutAlignment.BOTTOM;
            } else if (getLayoutConfig() instanceof DependentLayout.LayoutConfig) {
                DependentLayout.LayoutConfig config = (DependentLayout.LayoutConfig) getLayoutConfig();
                config.removeRule(DependentLayout.LayoutConfig.ALIGN_PARENT_TOP);
                config.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_BOTTOM);
            }
            setAnimationInterpolator(new LinearInterpolator(), null);
        } else {
            if (getLayoutConfig() instanceof StackLayout.LayoutConfig) {
                ((StackLayout.LayoutConfig) getLayoutConfig()).alignment = LayoutAlignment.TOP;
            } else if (getLayoutConfig() instanceof DependentLayout.LayoutConfig) {
                DependentLayout.LayoutConfig config = (DependentLayout.LayoutConfig) getLayoutConfig();
                config.removeRule(DependentLayout.LayoutConfig.ALIGN_PARENT_BOTTOM);
                config.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_TOP);
            }
            setAnimationInterpolator(new BounceInterpolator(), null);
        }
        if (intent != null) {
            intent.setParam("position", position);
        }
        return this;
    }

    public Toast setText(String msg) {
        if (messageTextView != null && msg != null && msg.length() > 0) {
            messageTextView.setText(msg);
        }
        if (intent != null && msg != null && msg.length() > 0) {
            intent.setParam("message", msg);
        }
        return this;
    }

    public Toast setTextColor(int color) {
        if (color == 0) return this;
        if (intent != null) {
            intent.setParam("textColor", color);
        } else {
            messageTextView.setTextColor(new Color(color));
        }
        return this;
    }

    public Toast setTime(long time) {
        if (time != 0) {
            this.time = time;
            if (intent != null) {
                intent.setParam("time", this.time);
            }
        }
        return this;
    }

    public void show() {
        show(0);
    }

    public void show(long delay) {
        if (this.getComponentParent() != null) {
            return;
        }
        delay = Math.max(delay, 0);
        if (mViewGroup != null) {
            mViewGroup.addComponent(this);
        } else if (intent != null) {
            intent.setParam("delay", delay);
            if (checkWindowPermission(getContext())) {
                FloatWindowService.onCommand(getContext(), intent);
            }
            return;
        }

        getContext().getUITaskDispatcher().delayDispatch(() -> {
            Toast.this.setVisibility(INVISIBLE);
            if (position == Position.BOTTOM) {
                setTranslationY(getHeight());
            } else {
                setTranslationY(-getHeight());
            }
            AnimatorValue animatorProperty = createTranslationAnim(this, getTranslationY(), 0, enterInterpolator == null ? new BounceInterpolator() : enterInterpolator);
            animatorProperty.setCurve(enterInterpolator == null ? new BounceInterpolator() : enterInterpolator);
            animatorProperty.setStateChangedListener(new AnimStateListener() {
                @Override
                public void onStart(Animator animator) {
                    super.onStart(animator);
                    for (ToastListener toastListener : mToastListeners) {
                        toastListener.beforeToastShow(Toast.this);
                    }
                    Toast.this.setVisibility(VISIBLE);
                    if (Toast.this.getComponentParent() instanceof Component && SERVICE_WINDOWS_PARENT.equals(((Component) Toast.this.getComponentParent()).getTag())) {
                        ((Component) Toast.this.getComponentParent()).setVisibility(VISIBLE);
                    }
                    for (ToastListener toastListener : mToastListeners) {
                        toastListener.afterToastShow(Toast.this);
                    }
                }
            });
            animatorProperty.start();
        }, BENCHMARK + delay);

        if (time != -1) {
            getContext().getUITaskDispatcher().delayDispatch(() -> {
                AnimatorValue animatorProperty = createTranslationAnim(this, getTranslationY(),
                        getTranslationY() + (position == Position.BOTTOM ? getHeight() : -getHeight()),
                        exitInterpolator == null ? new AccelerateInterpolator() : exitInterpolator);
                animatorProperty.setStateChangedListener(new AnimStateListener() {
                    @Override
                    public void onEnd(Animator animator) {
                        super.onEnd(animator);
                        for (ToastListener toastListener : mToastListeners) {
                            toastListener.beforeToastDismiss(Toast.this);
                        }
                        if (Toast.this.getComponentParent() instanceof Component
                                && SERVICE_WINDOWS_PARENT.equals(((Component) Toast.this.getComponentParent()).getTag())) {
                            ((Component) Toast.this.getComponentParent()).setVisibility(HIDE);
                        }
                        if (Toast.this.getComponentParent() instanceof ComponentContainer) {
                            Toast.this.getComponentParent().removeComponent(Toast.this);
                        }
                        for (ToastListener toastListener : mToastListeners) {
                            toastListener.afterToastDismiss(Toast.this);
                        }
                    }
                });
                animatorProperty.start();
            }, time > 0 ? delay + time + DEFAULT_DURATION : delay + DEFAULT_TIME + DEFAULT_DURATION);
        }
    }

    public enum Position {
        TOP,
        BOTTOM,
    }

    /**
     * Create an animation of displayed or hidden value changes
     */
    private AnimatorValue createTranslationAnim(Component view, float start, float end, Animator.TimelineCurve timelineCurve) {
        AnimatorValue va = new AnimatorValue();
        va.setDuration(DEFAULT_DURATION);
        va.setCurve(timelineCurve);
        va.setValueUpdateListener((animatorValue, v) -> {
            float result = (float) calAnimValue(start, end, v);
            view.setTranslationY(result);
        });
        return va;
    }

    /**
     * According to the current value of the animation, calculate the actual value between the start value and the end value
     */
    private double calAnimValue(double start, double end, double curr) {
        return start + (end - start) * curr;
    }

    /**
     * 动画状态监听
     */
    private static class AnimStateListener implements Animator.StateChangedListener {

        @Override
        public void onStart(Animator animator) {
        }

        @Override
        public void onStop(Animator animator) {
        }

        @Override
        public void onCancel(Animator animator) {
        }

        @Override
        public void onEnd(Animator animator) {
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    }
}
